//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.IO;
using System.Security;
using System.Text.RegularExpressions;
using System.Web.Caching;
using CommunityServer.Components;
using Telligent.CommunityServer.MetadataExtractor;
using Telligent.CommunityServer.MetadataExtractor.Exif;
using Telligent.CommunityServer.MetadataExtractor.Imaging.Jpeg;

namespace CommunityServer.Galleries.Components
{
	/// <summary>
	/// Summary description for Pictures.
	/// </summary>
	public class GalleryPosts
	{

		#region Preview

		public static void PreviewFormat(GalleryPost post)
		{
			PostFormatting(post);
			CSEvents.BeforePost(post, ObjectState.Create, ApplicationType.Gallery);
		}

		#endregion

		#region Add

		public static void CreatePicture(int galleryID, GalleryPost post, PostAttachment pictureData)
		{
			CreatePicture(galleryID, post, pictureData, true);
		}
		public static void CreatePicture(int galleryID, GalleryPost post, PostAttachment pictureData, bool formatPost)
		{
			Metadata metadata = new Metadata();

			CSContext cSContext = CSContext.Current;

			// Make sure some basic settings exist
			post.SectionID = galleryID;
			post.Username = cSContext.User.Username;
			post.User = cSContext.User;
			post.User.UserID = cSContext.User.UserID;
			post.AuthorID = cSContext.User.UserID;
			post.Attachment = pictureData;

			ProcessAttachment(post, ref metadata, pictureData);

			// Allow internal components to skip formatting for "quick" updates such as approving a post
			if (formatPost)
				PostFormatting(post);

			if (!post.IsPicture)
				return;


			CSEvents.PrePost(post, ObjectState.Create, ApplicationType.Gallery);


			GalleryDataProvider galleryDB = GalleryDataProvider.Instance();
			if (post.PostID == 0)
			{
				// Fire some events before posting to the DB
				CSEvents.BeforePost(post, ObjectState.Create, ApplicationType.Gallery);

				// Add it to the database
				galleryDB.CreatePicture(post);
			}
			else
			{
				DeletePictureData(post);
				UpdatePicture(post);
			}

			// Make sure it was successfully added a post and then add the picture itself
			if (post.PostID > 0)
			{
				if (post.Categories != null)
				{
					// Update in the database
					GalleryDataProvider.Instance().UpdatePictureCategories(post.SectionID, post.PostID, post.Categories);

					PostCategories.ExpireCategoryCache(post.SectionID);

					// Clear the cache
					foreach (string name in post.Categories)
					{
						PostCategory category = PostCategories.GetCategory(name, post.SectionID, true);
						if (category != null)
							ClearPicturesCache(post.SectionID, category.CategoryID, -1);
					}
				}

				// If we have any metadata, store it
				if (metadata.GetDirectoryCount() > 0)
					galleryDB.UpdatePictureMetadata(post.PostID, metadata);

				CSEvents.BeforePostAttachment(post, pictureData, ObjectState.Create, ApplicationType.Gallery);
				CreatePictureData(post, pictureData);
				CSEvents.AfterPostAttachment(post, pictureData, ObjectState.Create, ApplicationType.Gallery);

				// Clear cache
				ClearPicturesCache(post.SectionID, -1, post.PostID);

				CSEvents.AfterPost(post, ObjectState.Create, ApplicationType.Gallery);
			}
		}

		public static void CreatePicture(PostAttachment pictureData)
		{
			CreatePicture(pictureData, true, null);
		}
		public static void CreatePicture(PostAttachment pictureData, bool makeApproved, string categories)
		{
			Gallery gallery = Galleries.GetGallery(pictureData.SectionID);

			if (gallery == null)
				throw new CSException(CSExceptionType.SectionNotFound, string.Format("Cannot add photo to {0} - Section Not Found", pictureData.SectionID));

			GalleryPost post = new GalleryPost();

			post.Username = CSContext.Current.User.Username;
			post.SectionID = gallery.SectionID;
			post.ParentID = 0;
			post.GalleryPostType = GalleryPostType.Image;
			post.PostID = -1;

			post.Subject = "";
			post.Excerpt = "";
			post.Body = "";
			post.PostType = PostContentType.HTML;
			post.FormattedBody = null;

			post.IsLocked = gallery.EnableRssCommentsOverride ? gallery.EnableRssCommentsDefault : false;

			post.IsApproved = makeApproved;

			post.EnableRatings = gallery.EnableRatingsOverride ? gallery.EnableRatingsDefault : false;
			post.EnableTrackBacks = gallery.EnableTrackbacksOverride ? gallery.EnableTrackbacksDefault : false;
			post.EnableOrderPrints = gallery.EnableOrderPrintsOverride ? gallery.EnableOrderPrints : false;
			post.EnableAllOwnerNotification = false;

			//these are only partly implemented
			//EnableYesNo("ynAggregatePost", PostConfigHelper.IsPostConfigSelected(gc, GalleryPostConfig.IsAggregated), isPost);
			//EnableYesNo("ynCommunity", PostConfigHelper.IsPostConfigSelected(gc, GalleryPostConfig.IsCommunityAggregated), isPost);
			//EnableYesNo("ynSyndicateExcerpt", PostConfigHelper.IsPostConfigSelected(gc, GalleryPostConfig.SyndicateExcerpt), isPost);

			if (categories != null && categories.Length > 0)
				post.Categories = categories.Split(',');

			post.PostDate = DateTime.Now;
			post.UserTime = UserTime.ConvertToUserTime(post.PostDate);

			// Create the picture in the database
			GalleryPosts.CreatePicture(post.SectionID, post, pictureData);

			//make the thumbnail
			ImageHandling.ScalePicture(post, gallery.GetThumbnailSettings());
		}
		#endregion

		#region Update

		public static void UpdatePicture(GalleryPost post)
		{
			UpdatePicture(post, true);
		}
		public static void UpdatePicture(GalleryPost post, bool formatPost)
		{
			CSContext cSContext = CSContext.Current;

			// Allow internal components to skip formatting for "quick" updates such as approving a post
			if (formatPost)
				PostFormatting(post);

			CSEvents.PrePost(post, ObjectState.Update, ApplicationType.Gallery);
			CSEvents.BeforePost(post, ObjectState.Update, ApplicationType.Gallery);


			// Update it in the database
			GalleryDataProvider.Instance().UpdatePicture(post, cSContext.User.UserID);

			if (post.Categories != null)
			{
				// Update in the database
				GalleryDataProvider.Instance().UpdatePictureCategories(post.SectionID, post.PostID, post.Categories);

				PostCategories.ExpireCategoryCache(post.SectionID);

				// Clear the cache
				foreach (string name in post.Categories)
				{
					PostCategory category = PostCategories.GetCategory(name, post.SectionID, true);
					if (category != null)
						ClearPicturesCache(post.SectionID, category.CategoryID, -1);
				}
			}

			// Clear cache
			ClearPicturesCache(post.SectionID, -1, post.PostID);

			CSEvents.AfterPost(post, ObjectState.Update, ApplicationType.Gallery);
		}

		#endregion

		public static void DeletePicture(GalleryPost galleryPost)
		{
			CSEvents.PrePost(galleryPost, ObjectState.Delete, ApplicationType.Gallery);
			CSEvents.BeforePost(galleryPost, ObjectState.Delete, ApplicationType.Gallery);

			// Delete the picture data
			DeletePictureData(galleryPost);

			// Delete it from the database
			GalleryDataProvider.Instance().DeletePicture(galleryPost.SectionID, galleryPost.ThreadID);

			// Clear cache
			ClearPicturesCache(galleryPost.SectionID, -1, galleryPost.PostID);

			CSEvents.AfterPost(galleryPost, ObjectState.Delete, ApplicationType.Gallery);
		}

		public static GalleryPost GetRandomPicture(int galleryID, int categoryID)
		{
			CSContext cSContext = CSContext.Current;
			string cacheKey = "RandomPicture-Gallery:" + galleryID + "-Category:" + categoryID;
			int postID;

			if (!cSContext.Items.Contains(cacheKey))
			{
				postID = GalleryDataProvider.Instance().GetRandomPostID(galleryID, categoryID);
				cSContext.Items.Add(cacheKey, postID);
			}
			else
				postID = (int)cSContext.Items[cacheKey];

			if (postID == -1)
				return null;
			else
				return GetPicture(postID);
		}

		/// <summary>
		/// Gets the first post in a category based on the administrative sort order
		/// </summary>
		public static GalleryPost GetFirstPicture(int galleryID, int categoryID)
		{
			CSContext cSContext = CSContext.Current;
			Gallery g = Galleries.GetGallery(galleryID);

			string cacheKey = string.Format("FirstPicture-Gallery:{0}-Category:{1}-SortBy:{2}-SortOrder:{3}", galleryID, categoryID, g.ThreadSortBy, g.ThreadSortOrder);
			int postID;
			if (!cSContext.Items.Contains(cacheKey))
			{
				GalleryThreadQuery query = new GalleryThreadQuery();
				query.SectionID = galleryID;
				query.CategoryID = categoryID;
				query.ApplicationPostType = GalleryPostType.Image;
				//query.PageIndex = currentPage.PageIndex = pager.PageIndex;
				query.PageSize = 1;

				query.SortBy = g.ThreadSortBy;
				query.SortOrder = g.ThreadSortOrder;
				query.PublishedFilter = GalleryPostPublishedFilter.Published;

				ThreadSet ts = GalleryPosts.GetPictures(query);

				if (ts.TotalRecords > 0)
					postID = ((Post)ts.Threads[0]).PostID;
				else
					postID = -1;

				cSContext.Items.Add(cacheKey, postID);
			}
			else
				postID = (int)cSContext.Items[cacheKey];

			if (postID == -1)
				return null;
			else
				return GetPicture(postID);
		}

		public static GalleryPost GetPicture(int pictureID, bool flush, bool includeCategories)
		{
			CSContext cSContext = CSContext.Current;
			int userID = cSContext.User.UserID;
			string cacheKey = string.Format("Picture-Site:{0}PictureID:{1}:Categories:{2}", cSContext.SiteSettings.SettingsID, pictureID, includeCategories);
			if (flush)
				cSContext.Items.Remove(cacheKey);

			GalleryPost galleryPost = cSContext.Items[cacheKey] as GalleryPost;
			if (galleryPost == null)
			{
				galleryPost = GalleryDataProvider.Instance().GetPicture(pictureID, userID, includeCategories);
				cSContext.Items.Add(cacheKey, galleryPost);
			}
			return galleryPost;
		}

		public static GalleryPost GetPicture(int pictureID, bool flush)
		{
			return GetPicture(pictureID, false, false);
		}

		public static GalleryPost GetPicture(int pictureID)
		{
			return GetPicture(pictureID, false);
		}

		public static GalleryPost GetPicture(string name)
		{
			CSContext cSContext = CSContext.Current;
			string cacheKey = string.Format("Picture-Site:{0}PictureName:{1}", cSContext.SiteSettings.SettingsID, name);

			GalleryPost galleryPost = cSContext.Items[cacheKey] as GalleryPost;

			if (galleryPost == null)
			{
				GalleryThreadQuery query = new GalleryThreadQuery();

				query.PostName = name;
				query.RequireSectionIsActive = true;

				ThreadSet ts = GalleryPosts.GetPictures(query, false);

				if (ts.TotalRecords > 0)
				{
					galleryPost = (GalleryPost)ts.Threads[0];
					cSContext.Items.Add(cacheKey, galleryPost);
				}
			}
			return galleryPost;
		}
		public static ThreadSet GetPictures(GalleryThreadQuery query)
		{
			return GetPictures(query, true);
		}

		public static ThreadSet GetPictures(GalleryThreadQuery query, bool cacheable)
		{
			return GetPictures(query, cacheable, false);
		}

		public static ThreadSet GetPictures(GalleryThreadQuery query, bool cacheable, bool isAggregate)
		{
			CSContext context = CSContext.Current;
			string key = cacheable ? string.Format("{0}{1}", query.Key, context.RolesCacheKey) : null;
			ThreadSet ts = null;

			if (cacheable)
				ts = CSCache.Get(key) as ThreadSet;

			if (ts == null)
			{
				ts = GalleryDataProvider.Instance().GetPictures(query);

				if (cacheable)
					CSCache.Insert(key, ts, CSCache.MinuteFactor * 5, isAggregate ? CacheItemPriority.High : CacheItemPriority.BelowNormal);
			}

			return ts;
		}

		public static void ClearPicturesCache(int galleryID, int categoryID, int pictureID)
		{
			CSContext cSContext = CSContext.Current;

			// Picture itself
			if (pictureID != -1)
				CSCache.Remove(string.Format("Picture-Site:{0}PictureID:{1}", cSContext.SiteSettings.SettingsID, pictureID));

			// Picture collections
			CSCache.RemoveByPattern(string.Format("Pictures-S:{0}", galleryID));
		}

		//TODO: work on new save attachement patterns
		private static void CreatePictureData(GalleryPost galleryPost, PostAttachment pictureData)
		{

			// Submit it to the database
			if (GalleryConfiguration.Instance().AttachmentSettings.EnableDataStoreStorage && pictureData.HasDateCreated)
				CommonDataProvider.Instance().AddPostAttachment(galleryPost, pictureData);
			else
			{
				PostAttachment noPictureData = new PostAttachment(pictureData);
				noPictureData.Content = null;
				CommonDataProvider.Instance().AddPostAttachment(galleryPost, noPictureData);
			}

			// If file system storage is enabled, we need to save it.
			if (GalleryConfiguration.Instance().AttachmentSettings.EnableFileSystemStorage)
				CreateDataFile(galleryPost, pictureData);

		}

		//TODO: work on new save attachement patterns
		private static void CreateDataFile(GalleryPost galleryPost, PostAttachment pictureData)
		{
			// Make sure the folder exists, if not, attempt to create it
			if (!GalleryConfiguration.Instance().AttachmentSettings.FileSystemStorageLocationExists)
			{
				new CSException(CSExceptionType.AccessDenied, "Gallery storage directory does not exist and could not create (" + GalleryConfiguration.Instance().AttachmentSettings.FileSystemStorageLocation + ")").Log();
				return;
			}

			FileStream stream = null;

			// Save the file
			string file = GalleryConfiguration.Instance().AttachmentSettings.GetLocalFileName(galleryPost);
			try
			{
				stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
				byte[] buffer = new byte[256 * 1024];
				int bytes;

				pictureData.Content.Position = 0;
				while ((bytes = pictureData.Content.Read(buffer, 0, 256 * 1024)) > 0)
					stream.Write(buffer, 0, bytes);
				stream.Flush();
			}
			catch
			{
				(new CSException(CSExceptionType.AccessDenied, "Permission to save picture denied (" + file + ")")).Log();
				return;
			}
			finally
			{
				if (stream != null)
					stream.Close();
			}
		}

		//TODO: work on new save attachement patterns
		private static void DeletePictureData(GalleryPost galleryPost)
		{
			string file = GalleryConfiguration.Instance().AttachmentSettings.GetLocalFileName(galleryPost);

			// Check to see if the file exists on the filesystem
			if (File.Exists(file))
			{
				// It does, so delete it
				File.Delete(file);
			}

			// Delete the remaining cache files
			try
			{
				GalleryConfiguration.Instance().CacheSettings.LegacyClearStorage(galleryPost.SectionID, galleryPost.PostID);
				GalleryConfiguration.Instance().CacheSettings.DeleteAttachmentFromDisk(galleryPost);
			}
			catch (IOException e)
			{ new CSException(CSExceptionType.AccessDenied, "DeletePictureData: Permission to delete cache file denied.", e).Log(); }
			catch (SecurityException e)
			{ new CSException(CSExceptionType.AccessDenied, "DeletePictureData: Permission to delete cache file denied.", e).Log(); }
			catch { }

			// Submit it to the database
			CommonDataProvider.Instance().DeletePostAttachment(galleryPost.PostID);
		}

		/// <summary>
		/// returns the PostAttachment object filled with the byte[] of the attachment...  using the disk locaiton to fill the array if present
		/// </summary>
		/// <param name="post"></param>
		/// <returns></returns>
		public static PostAttachment GetPictureData(GalleryPost post)
		{
			CSContext.Current.PostID = post.PostID;
			return GalleryConfiguration.Instance().AttachmentSettings.GetAttachment();
		}

		public static void EnsureDirectOriginalFile(GalleryPost post)
		{
			string file = GalleryConfiguration.Instance().AttachmentSettings.GetLocalFileName(post);
			if (!File.Exists(file))
				GetPictureData(post); //this will try to make the file from the database

		}

		public static ArrayList GetPictureCategories(GalleryPost galleryPost)
		{
			return GalleryDataProvider.Instance().GetPictureCategories(galleryPost.PostID);
		}

		public static void UpdatePictureMetadata(int postID, Metadata metadata)
		{
			GalleryDataProvider.Instance().UpdatePictureMetadata(postID, metadata);
		}

		public static Metadata GetPictureMetadata(int postID)
		{
			CSContext cSContext = CSContext.Current;
			string cacheKey = "PictureMetadata-PostID:" + postID;

			Metadata metadata = cSContext.Items[cacheKey] as Metadata;

			if (metadata == null)
			{
				metadata = GalleryDataProvider.Instance().GetPictureMetadata(postID);
				cSContext.Items.Add(cacheKey, metadata);
			}

			return metadata;
		}

		public static PostSet GetTrackedThreadsByUser(int GalleryID, int UserID)
		{
			GalleryDataProvider gdp = GalleryDataProvider.Instance();
			return gdp.GetThreadsTrackedByUser(GalleryID, UserID);
		}



		# region Feedback

		public static GalleryPost Add(GalleryPost gp, User user)
		{
			gp.User = user;
			return CreateComment(gp.ParentID, gp);

		}
		public static GalleryPost CreateComment(int pictureID, GalleryPost post)
		{
			GalleryPost parentPost = GalleryPosts.GetPicture(pictureID);

			// Setup the ThreadID and parent PostID
			post.ThreadID = parentPost.ThreadID;
			post.ParentID = parentPost.PostID;


			//Let's not allow any markup except links in comments. 
			post.FormattedBody = post.Body;
			post.Subject = Globals.HtmlEncode(post.Subject);

			CSEvents.PrePost(post, ObjectState.Create, ApplicationType.Gallery);
			CSEvents.BeforePost(post, ObjectState.Create, ApplicationType.Gallery);

			// Submit it to the database
			GalleryDataProvider.Instance().CreateComment(post);

			CSEvents.AfterPost(post, ObjectState.Create, ApplicationType.Gallery);

			// Return it for use with sending the email
			return post;
		}

		public static void ApproveComment(int postID)
		{
			GalleryDataProvider.Instance().ApprovePost(postID, CSContext.Current.User.UserID);
		}

		public static void DeleteComment(int postID)
		{
			GalleryDataProvider.Instance().DeletePost(postID, CSContext.Current.User.UserID);
		}

		public static PostSet GetPostSet(GalleryThreadQuery query)
		{
			GalleryDataProvider gdp = GalleryDataProvider.Instance();

			query.ApplicationPostType = GalleryPostType.Comment | GalleryPostType.Image | GalleryPostType.Trackback;
			query.IncludeCategories = false;
			query.ReturnFullThread = true;
			return gdp.GetPosts(query);

		}


		#endregion

		#region Post Helpers

		internal static void ProcessEmails(GalleryPost post)
		{
			if (post.IsPostEnabled && post.EnableExternalNotificatons)
			{
				if (post.GalleryPostType == GalleryPostType.Image)
				{
					//TODO: WeblogEmails.WeblogTracking(post,null,post.Weblog);

				}
				else
				{
					GalleryPost parent = GalleryPosts.GetPicture(post.ParentID);
					if (parent != null)
					{
						//TODO: WeblogEmails.WeblogTracking(post,parent,post.Weblog);
					}
				}
			}
		}

		public static readonly string PostNamePattern = "^[0-9]*[^0-9].{0,224}$";

		/// <summary>
		/// Provides formatting/security support on weblog posts
		/// </summary>
		internal static void PostFormatting(GalleryPost post)
		{
			CSContext cSContext = CSContext.Current;

			if (post.HasName)
			{
				if (!Regex.IsMatch(post.Name, PostNamePattern, RegexOptions.IgnoreCase))
					throw new ArgumentException(string.Format("Invalid Post Name format. Must match pattern {0}", PostNamePattern));
			}

			post.SubmittedUserName = Globals.HtmlEncode((post.SubmittedUserName));
			post.Subject = Globals.HtmlEncode(post.Subject);

			if (post.PostID <= 0)
			{
				// Are we tracking the post IP address?
				if (cSContext.IsWebRequest && cSContext.SiteSettings.EnableTrackPostsByIP)
				{
					string ip = cSContext.Context.Request.UserHostAddress;
					if (ip != null)
						post.UserHostAddress = ip;
				}
			}


			if (post.GalleryPostType == GalleryPostType.Image)
			{
				//We are enabling other parts of the application to pre-process the post data and store the original in the body
				//property. If we find a value in the .FormattedBody property we will use it as our base

				if (Globals.IsNullorEmpty(post.FormattedBody))
					post.FormattedBody = post.Body;
			}
			else
			{
				post.FormattedBody = post.Body;
			}

			if (post.IsPostEnabled && post.EverTracked == false)
			{
				post.EverTracked = true;
				post.EnableExternalNotificatons = true;
			}

		}

		internal static void ProcessAttachment(GalleryPost post, ref Metadata metadata, PostAttachment pictureData)
		{

			// Check to make sure it is a valid image
			ImageInfo imageInfo = new ImageInfo(pictureData.Content);
			if (post.Body == string.Empty)
				imageInfo.CollectComments = true;
			else
				imageInfo.CollectComments = false;

			// If it is a picture, get the imagesize and EXIF data
			if (post.IsPicture && imageInfo.Check())
			{
				// Get comments from the image, if enabled
				if (imageInfo.CollectComments && (imageInfo.Comments != null))
				{
					foreach (string comment in imageInfo.Comments)
						post.Body += comment;
					post.FormattedBody = post.Body;
				}

				// Get the image's width and height settings
				pictureData.ContentType = imageInfo.MimeType;
				post.Attachment.Width = imageInfo.Width;
				post.Attachment.Height = imageInfo.Height;

				// Get the metadata from the picture if it is a JPEG
				try
				{
					pictureData.Content.Position = 0;
					if (imageInfo.Format == ImageInfo.FORMAT_JPEG)
						metadata = JpegMetadataReader.ReadMetadata(pictureData.Content);
				}
				catch { }

				// Set subject if there is none
				if (Globals.IsNullorEmpty(post.Subject))
					post.Subject = pictureData.FileName;
			}
			else
				return;

		}
		#endregion

	}
}